﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Medianamik.Core.Configuration;
using System.Configuration;
using System.Threading;
using Medianamik.Core.Reports;

namespace Medianamik.Core.Urls
{
    public class UrlFastDictionary : IUrlDictionary
    {
        private IPathSegmentFormater _segmentFormater;
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
        private IReportManager<UrlDictionaryReport> _reportManagerManager;
        private MedianamikConfigSection _config;

        private readonly UrlFastDomainDictionary _domainDictionary = new UrlFastDomainDictionary();
        private UrlFastDomainDictionary DomainDictionary
        {
            get
            {
                return _domainDictionary;
            }
        }

        private IReportManager<UrlDictionaryReport> ReportManagerManager
        {
            get { return _reportManagerManager; }
        }

        private void Add(IEnumerable<IUrlDictionaryEntry> entries)
        {
            if (UrlDictReport != null)
                UrlDictReport.NumberTotalOfEntries += entries.Count();

            entries.ForEach(AddUniqueEntry);
        }

        public void Initialize(MedianamikConfigSection config, IPathSegmentFormater segmentFormater, IReportManager<UrlDictionaryReport> reportManager)
        {
            _segmentFormater = segmentFormater;
            _config = config ?? (MedianamikConfigSection)ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK);
            _reportManagerManager = reportManager ?? new UrlDictionayEmailReportManager(_config.SendUrlDictionaryReportTo,
                "UrlDictionary Report from: " + (ConfigurationManager.AppSettings["ProbeWebSiteBaseURL"] ?? ""));

            LoadEntries();

            SendReport();

            Initialized = true;
        }

        public void Initialize(MedianamikConfigSection config)
        {
            Initialize(config, new PathSegmentFormater(), null);
        }

        private void LoadEntries()
        {
            LoadEntries(ReportAction.Initialized);
        }

        private void LoadEntries(ReportAction action)
        {
            WriteLock(() =>
            {
                DomainDictionary.Clear();
                //Urls.Clear();
                UrlDictReport = new UrlDictionaryReport();

                try
                {
                    Add(GetUrlDictionaryEntries());
                    Add(GetDefaultDocumentEntries());
                    UrlDictReport.ReportedReportAction = action;
                }
                catch (Exception ex)
                {
                    UrlDictReport.ReportedReportAction = ReportAction.Failure;
                }

                UrlDictReport.NumberOfDistinctEntries = DomainDictionary.GetNumberOfDistinctEntries();
            });
        }

        protected virtual IEnumerable<IUrlDictionaryEntry> GetDefaultDocumentEntries()
        {
            var webSites = ConfigManager.GetAll<WebSite>();

            var websitesInstances = (from website in webSites
                                     from instance in website.Instances
                                     select new
                                     {
                                         DefaultDocument = instance["DefaultDocument"].GetValue<Guid?>(),
                                         Prefix = instance["Prefix"].GetValue<string>(),
                                         NodeId = instance.Node.ID,
                                         CultureName = instance.Culture.Name,
                                     }).ToList();

            var langs = ConfigManager.GetEditionCultures().ToList();

            var defaultDocs = new List<UrlDictionaryEntry>();
            foreach (var websiteInstance in websitesInstances)
            {
                // Rechercher le document par défaut de chaque site Web
                foreach (var domain in DomainDictionary.Values)
                {
                    if (websiteInstance.DefaultDocument.HasValue)
                    {
                        var nodeDictionary = domain.GetByNodeId(websiteInstance.DefaultDocument.Value);

                        if (nodeDictionary != null)
                        {
                            foreach (IUrlDictionaryEntry entry in nodeDictionary.Values)
                            {
                                if (Equals(entry.WebsiteId, websiteInstance.NodeId) && String.Equals(entry.Culture.Name, websiteInstance.CultureName, StringComparison.InvariantCultureIgnoreCase))
                                {
                                    var wi = websiteInstance;

                                    defaultDocs.Add(
                                        new UrlDictionaryEntry(websiteInstance.DefaultDocument.Value,
                                           entry.TypeId, entry.ParentId, entry.HierarchicalPath,
                                           entry.Position, entry.HierarchicalDepth, entry.LinkOnFirstChild,
                                           webSites.First(w => w.ID.Equals(wi.NodeId)),
                                           entry.Culture, entry.Revision, entry.Generation,
                                           entry.Scheme, entry.Domain, entry.DomainGroup,
                                           entry.DomainCulture, "/", entry.RewritedPath,
                                           entry.ExcludedFromIndexation));
                                }
                            }
                        }
                    }
                }
            }
            /*
            //Ajout des documents par defaut
            var defaultDocs = (from entry in Urls
                               join wi in websitesInstances on entry.Value.NodeId equals wi.DefaultDocument
                               where string.Equals(entry.Value.Culture.Name, wi.CultureName, StringComparison.InvariantCultureIgnoreCase)
                               where Equals(wi.NodeId, entry.Value.WebsiteId)
                               select new UrlDictionaryEntry(wi.DefaultDocument.Value,
                                 webSites.First(w => w.ID.Equals(wi.NodeId)), entry.Value.Culture, entry.Value.Revision,
                                 entry.Value.Generation, entry.Value.Scheme, entry.Value.Domain,
                                 entry.Value.DomainGroup, entry.Value.DomainCulture, "/", entry.Value.RewritedPath,
                                 entry.Value.ExcludedFromIndexation));
             * */

            //Ok on a la liste des documents par défaut mais attention
            //il peut exister deux fois la meme URL par défaut pour deux langues différentes
            //quand le préfixe du website est identique dans les deux langues
            //Il faut donc ordonner la liste en fonction de la priorité des langues dans Medianamik
            var defaultDocumentEntries = defaultDocs
                .Where(d => langs.Any(l => l.CultureInfo == d.Culture))
                .Select(d => new
                {
                    Doc = d,
                    langs.First(l => l.CultureInfo == d.Culture).Position
                })
                .OrderBy(d => d.Position)
                .Select(d => d.Doc);

            return defaultDocumentEntries.Cast<IUrlDictionaryEntry>().ToList();
        }

        protected virtual IEnumerable<IUrlDictionaryEntry> GetUrlDictionaryEntries()
        {
            return ConfigManager.GetUrlDictionaryEntries();
        }

        private void SendReport()
        {
            if (_config.SendUrlDictionaryReport)
                ReportManagerManager.Send(UrlDictReport);
        }

        private void AddUniqueEntry(IUrlDictionaryEntry entry)
        {
            if (!DomainDictionary.ContainsKey(entry.Domain))
            {
                DomainDictionary.Add(entry.Domain, new UrlFastDomain());
            }

            IUrlDictionaryEntry result;

            if (DomainDictionary.TryGetEntryByPath(entry.LiteralRequestedUrl, out result))
            {
                UrlDictReport.DuplicatedEntries.Add(entry);
            }
            else
            {
                var domain = DomainDictionary[entry.Domain];
                domain.AddEntry(entry);
            }
        }

        public static UrlFastDictionary Instance
        {
            get
            {
                return Singleton<UrlFastDictionary>.Instance;
            }
        }

        protected internal UrlDictionaryReport UrlDictReport
        {
            get;
            set;
        }

        public void Reload()
        {
            if (!Initialized)
                throw new Exception("Reload is called but never been initilized");

            LoadEntries(ReportAction.Reload);
            SendReport();
        }

        private T ReadLock<T>(Func<T> func)
        {
            _lock.EnterReadLock();
            try { return func(); }
            finally { _lock.ExitReadLock(); }
        }

        private void UpgradeableReadLock(System.Action action)
        {
            _lock.EnterUpgradeableReadLock();
            try { action(); }
            finally { _lock.ExitUpgradeableReadLock(); }
        }

        private void WriteLock(System.Action action)
        {
            _lock.EnterWriteLock();
            try { action(); }
            finally { _lock.ExitWriteLock(); }
        }

        public IUrlDictionaryEntry GetEntry(string path)
        {
            return ReadLock(() =>
                {
                    IUrlDictionaryEntry result;

                    return DomainDictionary.TryGetEntryByPath(path, out result) ? result : null;
                });
            /*
            return ReadLock(() =>
            {
                IUrlDictionaryEntry result;
                return Urls.TryGetValue(path, out result) ? result : null;
            });*/
        }

        public IList<IUrlDictionaryEntry> GetEntries(Guid nodeId, CultureInfo culture)
        {
            return ReadLock(() =>
            {
                var allEntries = new List<IUrlDictionaryEntry>();

                foreach (var domain in DomainDictionary.Values)
                {
                    var node = domain.GetByNodeId(nodeId);
                    if (node != null)
                        allEntries.AddRange(node.Values);
                }

                if (culture == null)
                {
                    return allEntries;
                }

                return allEntries
                    .Where(u => u.NodeCulture.Name == culture.Name)
                    .OrderBy(u => u.DomainCulture.EqualsIgnoreCase(culture.Name)).ToList();
                /*
                if (culture == null)
                {
                    return (from url in Urls where url.Value.NodeId.Equals(nodeId) select url.Value).ToList();
                }

                return (from url in Urls
                        where url.Value.NodeIdCultureKey.Equals(new MultiKeyDictionaryKey(nodeId, culture))
                        orderby url.Value.DomainCulture.EqualsIgnoreCase(culture.Name)
                        select url.Value).ToList();*/
            });
        }

        public bool ContainsUrl(string path)
        {
            IUrlDictionaryEntry entry = GetEntry(path);
            return entry != null;
            //return ReadLock(() => Urls.ContainsKey(path));
        }

        public void Remove(Guid nodeId)
        {
            Remove(nodeId, false);
        }

        public void Remove(Guid nodeId, bool doNotRemoveRoot)
        {
            var toRemoveList = new List<IUrlDictionaryEntry>();
            foreach (var domain in DomainDictionary.Values)
            {
                var instanceDict = domain.GetByNodeId(nodeId);
                if (instanceDict != null)
                {
                    foreach (var entry in instanceDict.Values)
                    {
                        if (!(doNotRemoveRoot && entry.RequestedUrl.AbsolutePath == "/") && entry.NodeId.Equals(nodeId))
                        {
                            toRemoveList.Add(entry);
                        }
                    }
                }
            }
            for (int i = 0; i < toRemoveList.Count; i++)
            {
                Remove(toRemoveList[i]);
            }
            /*
            UpgradeableReadLock(() => Remove(doNotRemoveRoot ?
                 (from url in Urls
                  where url.Value.NodeId.Equals(nodeId) && url.Value.RequestedUrl.AbsolutePath != "/"
                  select url.Value).ToList() :
                 (from url in Urls
                  where url.Value.NodeId.Equals(nodeId)
                  select url.Value).ToList()));*/
        }

        public void Remove(Guid nodeId, CultureInfo culture)
        {
            var toRemoveList = new List<IUrlDictionaryEntry>();
            foreach (var domain in DomainDictionary.Values)
            {
                var instanceDict = domain.GetByNodeId(nodeId);
                foreach (var entry in instanceDict.Values.Where(entry => entry.Culture.Equals(culture)))
                {
                    toRemoveList.Add(entry);
                }
            }
            for (int i = 0; i < toRemoveList.Count; i++)
            {
                Remove(toRemoveList[i]);
            }
            /*
            UpgradeableReadLock(() => Remove((from url in Urls
                                              where url.Value.NodeId.Equals(nodeId) && url.Value.Culture.Equals(culture)
                                              select url.Value).ToList()));*/
        }

        private void Remove(IUrlDictionaryEntry entry)
        {
            foreach (var domain in DomainDictionary.Values)
            {
                domain.Remove(entry);
            }
        }

        public void UpdateUrlsFor(Guid nodeId)
        {
            UrlDictReport = new UrlDictionaryReport();
            UrlDictReport.ReportedReportAction = ReportAction.UpdateNodeEntries;
            UrlDictReport.UpdatedNodeId = nodeId;
            UrlDictReport.NumberTotalOfEntriesBeforeRemove = DomainDictionary.GetNumberOfDistinctEntries();

            Remove(nodeId, true);

            var entries = GetUrlDictionayEntries(nodeId);
            var numberOfEntries = entries.Count();

            if (numberOfEntries > 0)
            {
                WriteLock(() =>
                {
                    UrlDictReport.NumberOfAddedEntries = numberOfEntries;
                    UrlDictReport.AddedEntries = entries.ToList();
                    entries.ForEach(AddUniqueEntry);
                    UrlDictReport.NumberTotalOfEntries = DomainDictionary.GetNumberOfDistinctEntries();
                });
            }

            SendReport();
        }

        protected virtual IEnumerable<IUrlDictionaryEntry> GetUrlDictionayEntries(Guid nodeId)
        {
            return ConfigManager.GetUrlDictionaryEntries(nodeId);
        }

        public IList<IUrlDictionaryEntry> GetEntries(CultureInfo culture, string domain, string urlFilter)
        {
            return ReadLock(() =>
                        {
                            IList<IUrlDictionaryEntry> responseList = new List<IUrlDictionaryEntry>();
                            if (DomainDictionary.ContainsKey(domain))
                            {
                                var fastDomain = DomainDictionary[domain];
                                foreach (var entry in fastDomain)
                                {
                                    if (entry.Culture.Equals(culture) && entry.Path.Contains(urlFilter.ToLower()))
                                        responseList.Add(entry);
                                }
                            }
                            return responseList;
                        });
            /*
            return ReadLock(() =>
                            from url in Urls
                            where
                                url.Value.Culture.Equals(culture)
                                && url.Value.Domain == domain
                                && url.Value.Path.Contains(urlFilter.ToLower())
                            select url.Value).ToList();
             * */
        }

        public IList<string> GetDomains()
        {
            return ReadLock(() => DomainDictionary.Select(d => d.Key).ToList());
            //return ReadLock(() => Urls.Select(e => e.Value.Domain).Distinct(StringComparer.OrdinalIgnoreCase).ToList());
        }

        public int Count
        {
            get { return ReadLock(() => DomainDictionary.GetNumberOfDistinctEntries()); }
        }

        public IList<IUrlDictionaryEntry> GetAllEntries()
        {
            return GetAllEntries(IndexationModes.All);
        }

        public IList<IUrlDictionaryEntry> GetAllEntries(IndexationModes indexation)
        {
            return ReadLock(() =>
            {
                var allentires = DomainDictionary.Values.SelectMany(domain => domain);

                if (indexation != IndexationModes.All)
                {
                    return indexation == IndexationModes.Excluded
                               ? allentires.Where(e => e.ExcludedFromIndexation).ToList()
                               : allentires.Where(e => !e.ExcludedFromIndexation).ToList();
                }

                return allentires.ToList();
            });
        }

        public bool Contains(IUrlDictionaryEntry entry)
        {
            return GetEntriesForRequestedPath(entry.LiteralRequestedUrl) != null;
        }

        public string BuildUniqueRelativeUrlWithoutPrefixAndExtension(PathSegmentCollection segments, CultureInfo culture, WebSite website)
        {
            website.DefaultCulture = culture;

            var path = UrlHelper.GetPathFromSegments(_segmentFormater, segments);

            var url = new MedianamikUrl(website, culture, path, false);

            if (!UrlIsUniqueForWebSite(url))
            {
                if (!UrlExistsForCulture(url))
                {
                    //Les autre pages existent dans d'autres cultures
                    //On regarde si on peut ajouter le CultureName au nom de la page
                    var modifiedUrl = new MedianamikUrl(website, culture,
                        url.OriginalPath + "-" + culture.Name.ToLowerInvariant(), false);

                    if (UrlIsUniqueForWebSite(modifiedUrl))
                        return modifiedUrl.OriginalPath;
                }

                while (true)
                {
                    var modifiedUrl = new MedianamikUrl(website, culture,
                        url.OriginalPath + "-" + Guid.NewGuid().ToString("N").Substring(0, 6), false);

                    if (UrlIsUniqueForWebSite(modifiedUrl))
                        return modifiedUrl.OriginalPath;
                }
            }

            return url.OriginalPath;
        }

        public bool UrlIsUniqueForWebSite(IMedianamikUrl url)
        {
            return ReadLock(() =>
            {
                return !DomainDictionary.Any(
                    d =>
                    d.Value.Any(
                        u =>
                        u.Path.Equals(url.Path, StringComparison.OrdinalIgnoreCase) &&
                        u.WebsiteId.Equals(url.WebsiteId)));
            });
            /*
                        return ReadLock(() => !Urls.Any(entry => entry.Value.Path.Equals(url.Path, StringComparison.OrdinalIgnoreCase) &&
                                                                 entry.Value.WebsiteId.Equals(url.WebsiteId)));
             * */
        }

        public bool UrlIsUniqueForNode(IMedianamikUrl url, Guid nodeId)
        {
            return ReadLock(() =>
            {
                return !DomainDictionary.Any(
                    d =>
                    d.Value.Any(
                        u =>
                        u.Path.Equals(url.Path, StringComparison.OrdinalIgnoreCase) &&
                        u.WebsiteId.Equals(url.WebsiteId) &&
                        !u.NodeId.Equals(nodeId)));
            });

            /*            return ReadLock(() => !Urls.Any(entry => entry.Value.Path.Equals(url.Path, StringComparison.OrdinalIgnoreCase) &&
                            entry.Value.WebsiteId.Equals(url.WebsiteId) && !entry.Value.NodeId.Equals(nodeId)));
             * */
        }

        public bool Initialized { get; private set; }

        private bool UrlExistsForCulture(IMedianamikUrl url)
        {
            return GetCulturesForPath(url.Path).Any(c => c.Equals(url.Culture));
        }

        private CultureInfo[] GetCulturesForPath(string path)
        {
            return GetEntriesForRequestedPath(path).Select(entry => entry.Culture).ToArray();
        }

        private IEnumerable<IUrlDictionaryEntry> GetEntriesForRequestedPath(string path)
        {
            return ReadLock(() => (from domain in DomainDictionary.Values
                                   from entry in domain
                                   where entry.Path.Equals(path, StringComparison.OrdinalIgnoreCase)
                                   select entry).ToList());
        }

        public bool UrlIsValid(string realPath)
        {
            return Regex.IsMatch(realPath, "^(/[0-9a-zA-Z_]+)+((/[0-9a-zA-Z_]+)*|(-[0-9a-zA-Z_]+)*)*(\\.[a-zA-Z]+)?$");
            //Doit commencer par un /
            //Ne doit pas contenir de répétitions de -
            //Peut avoir une extension .html, .apsx, etc.
        }
    }
}